﻿using System;
using System.Collections.Generic;
using System.Text;
using PersistentObjects.DAL;

namespace PersistentObjects
{
    public static class DDL<T> where T:Persistent
    {
        public static void CreateTable ()
        {
            Persistent.Database.CreateTable(typeof(T));
        }

        public static void DeleteTable()
        {
            Persistent.Database.DeleteTable(typeof(T));
        }
    }

    public class PersistentList<T> : System.Collections.Generic.List<T> where T : Persistent
    {
        //TODO Add an offline sorting and selecting mechanism. Sorting should be easy but selecting... I should have a second list with the new items and add from that with every update.

        List<SelectWhereClause> selectWhereClauses;
        List<SelectOrderByClause> selectOrderByClauses;

        public List<SelectWhereClause> Select
        {
            get { return selectWhereClauses; }
        }
        public List<SelectOrderByClause> OrderBy
        {
            get { return selectOrderByClauses; }
        }

        public PersistentList()
            : this(true)
        {
        }

        public PersistentList(bool Fill)
        {
            selectWhereClauses = new List<SelectWhereClause>();
            selectOrderByClauses = new List<SelectOrderByClause>();
            if (Fill)
                Update();
        }

        [Obsolete]
        public PersistentList(T Example)
        {
            
            DAL.DatabaseReader reader = new DatabaseReader(Persistent.Database.FindItems(Example));
            while (reader.Read())
            {
                this.Add((T)Persistent.RetrievePersistentObject(reader, typeof(T)));
            }
            reader.Close();
        }

        public PersistentList(SelectWhereClause[] WhereClauses)
        {
            selectWhereClauses = new List<SelectWhereClause>(WhereClauses);
            selectOrderByClauses = new List<SelectOrderByClause>();
            Update();
        }

        public PersistentList(SelectOrderByClause[] OrderByClauses)
        {
            selectWhereClauses = new List<SelectWhereClause>();
            selectOrderByClauses = new List<SelectOrderByClause>(OrderByClauses);
            Update();
        }

        public PersistentList(SelectWhereClause[] WhereClauses, SelectOrderByClause[] OrderByClauses)
        {
            selectWhereClauses = new List<SelectWhereClause>(WhereClauses);
            selectOrderByClauses = new List<SelectOrderByClause>(OrderByClauses);
            Update();
        }

        public void Update()
        {
            Update(false);
        }
        public void Update(bool Persist)
        {
            if (Persist)
                this.Persist();
            Clear();
            DAL.DatabaseReader reader = new DatabaseReader(Persistent.Database.FindItems(typeof(T), selectWhereClauses.ToArray(), selectOrderByClauses.ToArray()));
            while (reader.Read())
            {
                this.Add((T)Persistent.RetrievePersistentObject(reader, typeof(T)));
            }
            reader.Close();
        }

        public void Persist()
        {
            foreach (Persistent item in this)
            {
                item.Persist();
            }
        }
    }

    public class SelectWhereClause : PersistentObjects.DAL.SelectWhereClause
    {
        public SelectWhereClause(string Field, string Operator, object Value)
            : base (Field, Operator, Value)
        {
        }

        public SelectWhereClause(string Field, object Value)
            : base (Field, Value)
        {
        }
    }

    public class SelectOrderByClause : PersistentObjects.DAL.SelectOrderByClause
    {
        public SelectOrderByClause(string Field, bool Ascending)
            : base(Field, Ascending)
        { }

        public SelectOrderByClause(string Field)
            : base(Field)
        { }
    }

    abstract public class Persistent
    {
        #region Fields
        static Database database = new Database("Data Source = undefined.sqlite; UseUTF16Encoding = True");
        [NonPersistent]
        private int OldHashCode;
        [NonPersistent]
        private Nullable<Int64> id;
        #endregion

        #region Methods
        internal static Persistent RetrievePersistentObject(Int64 ID, System.Type PersistentType)
        {
            if (!PersistentType.IsSubclassOf(typeof(Persistent)))
                throw new Exception();
            Persistent ret = (Persistent)database.Retrieve(ID, PersistentType);
            ret.id = ID;
            ret.OldHashCode = ret.GetHashCode();
            return ret;
        }

        internal static Persistent RetrievePersistentObject(DatabaseReader Reader, System.Type PersistentType)
        {
            if (!PersistentType.IsSubclassOf(typeof(Persistent)))
                throw new Exception();
            Persistent ret = (Persistent)database.Retrieve(Reader, PersistentType);
            ret.id = Reader.GetInt64(Reader.FieldCount - 1);
            ret.OldHashCode = ret.GetHashCode();
            return ret;
        }

        public void Persist()
        {
            switch (State)
            {
                case ItemState.Unsaved:
                    id = database.Insert(this);
                    OldHashCode = GetHashCode();
                    break;
                case ItemState.Changed:
                    database.Update(this.id.Value, this);
                    OldHashCode = GetHashCode();
                    break;
            }

            System.Type itemType = this.GetType();
            foreach (System.Reflection.PropertyInfo property in itemType.GetProperties())
            {
                if (property.PropertyType.IsGenericType)
                {
                    if (property.PropertyType.GetGenericArguments()[0].IsSubclassOf(typeof(Persistent)))
                    {
                        System.Collections.IEnumerable children = ((System.Collections.IEnumerable)property.GetValue(this, null));
                        foreach (Persistent child in children)
                        {
                            child.Persist();
                        }
                    }
                }
            }
        }
        #endregion

        #region Properties
        internal Int64? ID
        {
            get { return id; }
        }
        public static Database Database
        {
            get { return database; }
        }
        public static string ConnectionString
        {
            get { return database.ConnectionString; }
            set { database.ConnectionString = value; }
        }
        public override int GetHashCode()
        {
            return database.ItemHashCode(this);
        }
        public enum ItemState { Unsaved, Saved, Changed };
        public ItemState State
        {
            get
            {
                if (!id.HasValue)
                    return ItemState.Unsaved;
                if (OldHashCode == GetHashCode())
                    return ItemState.Saved;
                else
                    return ItemState.Changed;
            }
        }

        public PersistentList<T> GetRelation<T>() 
            where T:Persistent
        {
            PersistentList<T> ret = new PersistentList<T>(false);
            ret.Select.Add(new SelectWhereClause(this.GetType().Name,id));
            ret.Update();
            return ret;
        }
        #endregion

        #region Constructors
        public Persistent()
        {
            id = null;
            OldHashCode = GetHashCode();
        }
        public Persistent(Int64 ID)
        {
            database.Retrieve(ID, this.GetType(), this);
            this.id = ID;
            OldHashCode = GetHashCode();
        }
        public Persistent(DatabaseReader Reader)
        {
            database.Retrieve(Reader, this.GetType(), this);
            this.id = Reader.GetInt64(Reader.FieldCount - 1);
            OldHashCode = GetHashCode();
        }
        #endregion
    }
}
